//
//  UIDevice+IdentifierAddition.m
//  MobileChecker
//
//  Created by ehanxuan on 2015/01/14.
//  Copyright (c) 2015年 Lymons. All rights reserved.
//

#import "UIDevice+IdentifierAddition.h"
#import <objc/runtime.h>
#import <dlfcn.h>
#import <mach/port.h>
#import <mach/kern_return.h>

#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <CoreTelephony/CTCarrier.h>

#import "SimCardHeader.h"

#include <sys/utsname.h>

#define kDeviceModelDetailNamesNameKey @"name"
#define kDeviceModelDetailNamesShortNameKey @"shortname"
#define kDeviceModelDetailGPSKey @"gps"

#define kIphoneModel @"iPhone"

#define BLANK_TEXT @"-"


static NSDictionary *__modelDetailNames;

static char serialNumberKey = '0';
static char carrierNameKey = '1';
static char machineTypeKey = '2';
static char machineShortNameKey = '3';
static char machineDetailNameKey = '4';
static char totalSpaceKey = '5';
static char hasGPSKey = '6';

@implementation UIDevice (IdentifierAddition)

- (NSString *) serialNumber
{
    NSString *serialNumber = objc_getAssociatedObject(self, &serialNumberKey);
    if (serialNumber) {
        return serialNumber;
    }
    
    void *IOKit = dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", RTLD_NOW);
    if (IOKit)
    {
        mach_port_t *kIOMasterPortDefault = dlsym(IOKit, "kIOMasterPortDefault");
        CFMutableDictionaryRef (*IOServiceMatching)(const char *name) = dlsym(IOKit, "IOServiceMatching");
        
        mach_port_t (*IOServiceGetMatchingService)(mach_port_t masterPort, CFDictionaryRef matching) = dlsym(IOKit, "IOServiceGetMatchingService");
        CFTypeRef (*IORegistryEntryCreateCFProperty)(mach_port_t entry, CFStringRef key, CFAllocatorRef allocator, uint32_t options) = dlsym(IOKit, "IORegistryEntryCreateCFProperty");
        kern_return_t (*IOObjectRelease)(mach_port_t object) = dlsym(IOKit, "IOObjectRelease");
        
        if (kIOMasterPortDefault && IOServiceGetMatchingService && IORegistryEntryCreateCFProperty && IOObjectRelease)
        {
            mach_port_t platformExpertDevice = IOServiceGetMatchingService(*kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
            if (platformExpertDevice)
            {
                CFTypeRef platformSerialNumber = IORegistryEntryCreateCFProperty(platformExpertDevice, CFSTR("IOPlatformSerialNumber"), kCFAllocatorDefault, 0);
                
                if (platformSerialNumber && CFGetTypeID(platformSerialNumber) == CFStringGetTypeID())
                {
                    serialNumber = [NSString stringWithString:(__bridge NSString *)platformSerialNumber];
                    CFRelease(platformSerialNumber);
                }
                IOObjectRelease(platformExpertDevice);
            }
        }
        dlclose(IOKit);
    }
    
    objc_setAssociatedObject(self, &serialNumberKey, serialNumber, OBJC_ASSOCIATION_RETAIN);
    
    return serialNumber;
}

- (NSString *)carrierName
{
    NSString *carrierName = objc_getAssociatedObject(self, &carrierNameKey);
    if (carrierName) {
        return carrierName;
    }
    
    CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init];
    CTCarrier *carrier = [netinfo subscriberCellularProvider];
    
    carrierName = carrier.carrierName;
    
    if (carrierName.length) {
        objc_setAssociatedObject(self, &carrierNameKey, carrierName, OBJC_ASSOCIATION_RETAIN);
    } else {
        carrierName = BLANK_TEXT;
    }
    
    return carrierName;
}

- (NSString *)machineType
{
    NSString *machineType = objc_getAssociatedObject(self, &machineTypeKey);
    if (machineType) {
        return machineType;
    }
    
    struct utsname systemInfo;
    uname(&systemInfo);
    machineType = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
    
    objc_setAssociatedObject(self, &machineTypeKey, machineType, OBJC_ASSOCIATION_RETAIN);
    
    return machineType;
}

- (NSString *)machineDetailName
{
    NSString *machineName = objc_getAssociatedObject(self, &machineDetailNameKey);
    if (machineName) {
        return machineName;
    }
    
    NSString *machineType = [self machineType];
    NSDictionary *modelDetailNameDict = [self modelDetailNameDictForPlatform:machineType];
    if (!modelDetailNameDict) {
        return machineType;
    }
    
    machineName = [modelDetailNameDict valueForKey:kDeviceModelDetailNamesNameKey];
    objc_setAssociatedObject(self, &machineDetailNameKey, machineName, OBJC_ASSOCIATION_RETAIN);
    
    return machineName;
}

- (NSString *)machineShortName
{
    NSString *machineName = objc_getAssociatedObject(self, &machineShortNameKey);
    if (machineName) {
        return machineName;
    }
    
    NSString *machineType = [self machineType];
    NSDictionary *modelDetailNameDict = [self modelDetailNameDictForPlatform:machineType];
    if (!modelDetailNameDict) {
        return machineType;
    }
    
    machineName = [modelDetailNameDict valueForKey:kDeviceModelDetailNamesShortNameKey];
    objc_setAssociatedObject(self, &machineShortNameKey, machineName, OBJC_ASSOCIATION_RETAIN);
    
    return machineName;
}

- (float)totalSpace
{
    NSNumber *totalSpace = objc_getAssociatedObject(self, &totalSpaceKey);
    if (totalSpace) {
        return totalSpace.floatValue;
    }
    
    NSError *error = nil;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
    NSDictionary *dictionary = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error:&error];
    
    if (dictionary) {
        totalSpace = [NSNumber numberWithFloat:[[dictionary objectForKey:NSFileSystemSize] floatValue] / 1024 / 1024];
    } else {
        totalSpace = [NSNumber numberWithFloat:0];
        NSLog(@"Error Obtaining File System Info: Domain = %@, Code = %ld", [error domain], (long)[error code]);
    }
    
    objc_setAssociatedObject(self, &totalSpaceKey, totalSpace, OBJC_ASSOCIATION_RETAIN);
    
    return totalSpace.floatValue;
}

- (BOOL)hasSimCard
{
    if ([[self carrierName] isEqual:BLANK_TEXT]) {
        // for pad
        return NO;
    }
    return ![CTSIMSupportGetSIMStatus() isEqualToString:kCTSIMSupportSIMStatusNotInserted];
}

- (BOOL)hasGPS
{
    NSNumber *hasGPS = objc_getAssociatedObject(self, &hasGPSKey);
    if (hasGPS) {
        return hasGPS.boolValue;
    }
    
    if ([self carrierName] && ![[self carrierName] isEqual:BLANK_TEXT]) {
        hasGPS = [NSNumber numberWithBool:YES];
        
    } else {
        NSString *machineType = [self machineType];
        NSDictionary *modelDetailDict = [self modelDetailNameDictForPlatform:machineType];
        if (!modelDetailDict) {
            hasGPS = [NSNumber numberWithBool:NO];
        } else {
            hasGPS = [modelDetailDict valueForKey:kDeviceModelDetailGPSKey];
        }
    }
    
    objc_setAssociatedObject(self, &hasGPSKey, hasGPS, OBJC_ASSOCIATION_RETAIN);
    
    return hasGPS.boolValue;
}

- (BOOL)hasVibration
{
    if ([kIphoneModel isEqualToString:[[UIDevice currentDevice] model]]) {
        return YES;
    }
    return NO;
}

#pragma mark - private
- (NSDictionary *)modelDetailNames
{
    if (__modelDetailNames == nil) {
        NSString *path = [[NSBundle mainBundle] pathForResource:@"DeviceModelDetail" ofType:@"plist"];
        __modelDetailNames = [[NSDictionary alloc] initWithContentsOfFile:path];
    }
    return __modelDetailNames;
}

- (NSDictionary *)modelDetailNameDictForPlatform:(NSString *)platform {
    NSDictionary *retVal = [[self modelDetailNames] objectForKey:platform];
    return retVal;
}

@end